package de.tabiteuea.tools;

import java.io.IOException;
import java.util.regex.Pattern;

import de.tabiteuea.gui.Activator;
import de.tabiteuea.gui.models.Language;
import de.uni_leipzig.asv.utils.Pretree;
import edu.stanford.nlp.tagger.maxent.MaxentTagger;

/**
 * Tries to convert a given word to its baseform. That means for
 * example that the input "bigger" should be reduced to "big".
 * <p>
 * This process takes two steps.
 * <ol>
 * <li>
 * Detect the part of speech of the input. This is done by
 * the Stanford University "NLP Part-Of-Speech (POS) Tagger". See
 * http://www-nlp.stanford.edu/software/tagger.shtml for details.
 * <li>
 * Reduce the input according to its word class. This will be
 * done by the University of Leipzig "BaseFormTool". See
 * http://wortschatz.uni-leipzig.de/~cbiemann/software/toolbox/index.htm for
 * details.
 * </ol> 
 * 
 * We highly appreciate the work of both development teams. Thank you!
 * 
 * @author Sebastian Lippert
 */

public class BaseFormHandler {

	private String verbTree;
	private String nounTree;
	private String adjectivesTree;
	private String taggerModel;
	private MaxentTagger tagger;
	private Pretree nPretree, aPretree, vPretree;

	/**
	 * Constructor.
	 * 
	 * Instantiates the handler and loads all presets depending on the given
	 * language.
	 * 
	 * @param language
	 *            The language of the given input
	 */

	public BaseFormHandler(Language language) {

		switch (language) {
		case en:
			nounTree = Activator
					.getAbsolutePath(IBaseFormConstants.EN_NOUN_TREE);
			verbTree = Activator
					.getAbsolutePath(IBaseFormConstants.EN_VERB_TREE);
			adjectivesTree = Activator
					.getAbsolutePath(IBaseFormConstants.EN_ADJ_TREE);
			taggerModel = Activator
					.getAbsolutePath(IBaseFormConstants.EN_STANDFORD_TAGGER_MODEL);
			break;
		case de:
			nounTree = Activator
					.getAbsolutePath(IBaseFormConstants.DE_NOUN_TREE);
			verbTree = Activator
					.getAbsolutePath(IBaseFormConstants.DE_VERB_TREE);
			adjectivesTree = Activator
					.getAbsolutePath(IBaseFormConstants.DE_ADJ_TREE);
			taggerModel = Activator
					.getAbsolutePath(IBaseFormConstants.DE_STANDFORD_TAGGER_MODEL);
			break;
		}

		try {
			tagger = new MaxentTagger(taggerModel);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		/*
		 * Create and load trees for nouns, verbs and adjectives. This costs
		 * more memory but is faster than reloading for each single word.
		 */

		nPretree = new Pretree();
		nPretree.load(nounTree);

		vPretree = new Pretree();
		vPretree.load(verbTree);

		aPretree = new Pretree();
		aPretree.load(adjectivesTree);

	}

	/**
	 * Reduce a given input word to its baseform or returns the original word, 
	 * if no reduction is necessary or possible.
	 * 
	 * @param word
	 *            The input word to be reduced
	 * @return Input baseform (might be equal to original input).
	 */

	public String getBaseForm(String word) {

		String input = word;
		String[] stringItems;
		String suffix = "";
		int index = 0;
		/*
		 * Detect the part of speech. 'N[A-Z]' means noun, 'AD' adjective and
		 * 'V[A-Z]' verb. Another return value means that no applicable kind of
		 * POS was detected.
		 */

		String s = getPartOfSpeech(word);

		/*
		 * Classification String s consists of an integer and a character
		 * part (possibly empty).
		 * In some cases a '#' followed by a String is added to s.
		 * 
		 * For example s could be '2n' , '3' or '3n#ge'.
		 * 
		 * The integer i defines that the last i characters of the input word
		 * should be deleted.
		 * 
		 * For example '3' means the reduction of 'bigger' to 'big'.
		 * 
		 * The character sequence abc means that after the deletion of i
		 * characters from the tail of the word abc should added to the input
		 * word.
		 * 
		 * For example '3aken' means the reduction of 'took' to 'taken'
		 * 
		 * The sequence #abc means that the last of occurrence of 'abc' within
		 * the input word should be deleted.
		 * 
		 * For example '1en#ge' means the reduction of 'aufgebaut' to 'aufbauen'
		 * ('aufgebaut' is the german expression for 'constructed')
		 */

		if (s.startsWith("N"))
			s = nPretree.classify(word);	// noun
		else if (s.startsWith("V"))
			s = vPretree.classify(word);	// verb
		else if (s.equals("AD"))
			s = aPretree.classify(word);	// adjective
		else
			return input;					// no reduction

		/*
		 * Get String part from output of classify() and create the suffix that
		 * should be added to the input.
		 */
		Pattern p = Pattern.compile("\\d");
		stringItems = p.split(s);

		for (String part : stringItems) {
			suffix += part;
		}

		/*
		 * Check if deletion of a substring is necessary and do so in case
		 * of a positive match.
		 */

		p = Pattern.compile("#");
		stringItems = p.split(suffix);

		if (stringItems.length > 1) {
			for (int i = 1; i < stringItems.length; i++) {
				index = word.toLowerCase().lastIndexOf(stringItems[i]);

				// Check if the String that should be removed is really
				// part of the word. (Sometimes ASV base form tool fails.)
				if (index < 0)
					continue;

				word = word.substring(0, index).concat(
						word.substring(stringItems[i].length() + index));
			}
			suffix = stringItems[0];
		}

		// get numeric part from the output of classify() method
		p = Pattern.compile("\\D");
		stringItems = p.split(s);

		if (stringItems == null
				|| (stringItems.length <= 0 && stringItems.length > 1))
			return word;
		else {
			try {
				index = Integer.parseInt(stringItems[0]);
			} catch (NumberFormatException e) {
				e.printStackTrace();
				return input;
			}
		}

		// something went wrong so return input
		if (index > input.length()) {
			return input;
		}

		// create input baseform
		return word.subSequence(0, word.length() - index).toString()
				.concat(suffix);
	}

	/**
	 * Uses Stanford NLP POS tagger to detect the word class and returns
	 * the according value. 
	 * 
	 * @param word
	 *            String for POS recognition
	 * @return Character that represents the POS of the input word.
	 * '<code>N</code>' is for noun, '<code>A</code>' for adjective and
	 * '<code>V</code>' for verb.
	 * Any other return value means that no applicable part of speech class was
	 * detected.
	 */

	private String getPartOfSpeech(String word) {

		/*
		 * The tagger returns a string of the following form:
		 * inputword/classification
		 * 
		 * The input 'house' for example returns 'house/NN'.
		 * 
		 * This method returns the first character after the '/' because we need
		 * no further info.
		 * 
		 * But the tagger has some "interesting features". I can not guarantee
		 * an exact result, in especially if the character encoding isn't equal
		 * to the system's default or the input contains the '€' sign. So in
		 * a case of emergency, "XY" will be returned.
		 */

		int i = word.length();
		String taggedInput = "XY";
		try {
			taggedInput = tagger.tagString(word).substring(i + 1, i + 3);
		} catch (Exception e) {
			return "XY";
		}
		return taggedInput;

	}

}
